/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.util;

import hextd.core.Context;
import hextd.core.ContextPlayer;
import hextd.core.GameGrid;
import hextd.core.GameMap;
import hextd.hex.Hex;
import hextd.core.MapInfo;
import hextd.tower.JsonTower;
import hextd.tower.JsonTowerOptions;
import hextd.tower.Tower;
import hextd.tower.TowerFactory;
import hextd.wave.Path;
import hextd.wave.PathNormal;
import hextd.wave.Wave;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * MapIO loads maps from files from mapInfo objects.
 * @author hylke
 */
public class MapIO {

	private static MD5 md5;

	public static ArrayList<String> getLevelIndex(URL fetchUrl) {
		ArrayList<String> levelNames = new ArrayList<String>();
		BufferedReader bufferedreader;
		try {
			//System.out.println("PanelLevelSelector::FetchFromUrl::file = " + fetchUrl.toString());
			URLConnection urlconnection = fetchUrl.openConnection();
			urlconnection.setConnectTimeout(30000);
			if (urlconnection.getContentEncoding() != null) {
				bufferedreader = new BufferedReader(new InputStreamReader(urlconnection.getInputStream(), urlconnection.getContentEncoding()));
			} else {
				bufferedreader = new BufferedReader(new InputStreamReader(urlconnection.getInputStream(), "utf-8"));
			}
		} catch (IOException _ex) {
			System.err.println("HexTD::readFile:: Can't read from " + fetchUrl);
			return levelNames;
		}

		String sLine1;
		try {
			while ((sLine1 = bufferedreader.readLine()) != null) {
				if (sLine1.trim().length() != 0) {
					levelNames.add(sLine1);
				}
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		Collections.sort(levelNames);
		return levelNames;
	}

	public static String generateMD5(String input) {
		if (md5 == null) {
			try {
				md5 = MD5.getInstance();
			} catch (NoSuchAlgorithmException ex) {
				Logger.getLogger(MapIO.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
		if (md5 != null) {
			return md5.hashData(input);
		} else {
			return "hash not available";
		}
	}

	public static void loadLevel(Context context, MapInfo mapInfo) {
		GameMap gameMap = mapInfo.getMapData();
		context.loadedGameMap = gameMap;
		GameGrid gameGrid = context.newGameGrid();

		context.resetTowerOptions();
		if (gameMap.getTowerOptions() != null) {
			for (JsonTowerOptions options : gameMap.getTowerOptions()) {
				context.setTowerOptions(options.type, options);
			}
		}

		context.setLevelTitle(gameMap.getTitle());
		context.setLevelInfo(gameMap.getInfo());
		context.setBackgroundImage(gameMap.getBackGroundImage());
		context.setCreditsStart(gameMap.getBudget());
		context.setInterestStart(gameMap.getInterestStart());
		context.setInterestIncrease(gameMap.getInterestIncrease());

		if (gameMap.orientation == 1) {
			gameGrid.setLeftToRight(false);
		} else {
			gameGrid.setLeftToRight(true);
		}
		gameGrid.fillWith(gameMap.getGridData());

		ArrayList<Wave> waves = context.getWaves();
		waves.clear();
		waves.addAll(gameMap.getWaves());
		for (Wave w : waves) {
			w.setContext(context);
		}

		ArrayList<Path> paths = context.getPaths();
		paths.clear();
		paths.addAll(gameMap.getPaths());
		for (Path p : paths) {
			p.setContext(context);
			p.finalise();
		}

		for (JsonTower t : gameMap.getTowers()) {
			Hex h = gameGrid.getHex(t.col, t.row);
			if (!h.hasTower()) {
				Tower newTower = TowerFactory.createTower(
						t.type, context, h, t.owner, Context.actionDelay);
				context.addTower(newTower);
				ContextPlayer cp = context.getPlayer(t.owner);
				if (!cp.canPay(t.type.price, true)) {
					cp.doReceive(t.type.price);
				}
				cp.queuePayment(t.type.price);
				if (t.options != null) {
					for (Entry<String, String> e : t.options.entrySet()) {
						JsonTower.optionKey key = JsonTower.optionKey.valueOf(e.getKey().toLowerCase());
						switch (key) {
							case s0:
								newTower.setMode(1, 0, Integer.parseInt(e.getValue()));
								break;

							case s1:
								newTower.setMode(1, 1, Integer.parseInt(e.getValue()));
								break;

							case s2:
								newTower.setMode(1, 2, Integer.parseInt(e.getValue()));
								break;

							case u:
								int times = Integer.parseInt(e.getValue());
								for (int i = 0; i < times; i++) {
									int upgradePrice = newTower.getUpgradePrice(true);
									cp.doReceive(upgradePrice);
									context.upgradeTower(newTower);
								}
								break;

							default:
								newTower.setSetting(key, e.getValue());
								break;
						}
					}
				}
			}
		}

	}

	/**
	 * Converts old format mapfile to new style mapObject.
	 * @param b BufferedReader containing old format map.
	 * @return GameMap object conaining map.
	 */
	public static GameMap readLevel(BufferedReader b) {

		GameMap gameMap = new GameMap();

		List<Wave> waves = gameMap.getWaves();
		List<PathNormal> paths = gameMap.getPaths();
		List<String> gridData = gameMap.getGridData();
		List<JsonTower> towers = gameMap.getTowers();

		String levelInfo = "";
		String sLine;
		char c;
		int i;
		int count;
		int lineNr = 0;
		try {
			boolean next = true;
			int current = 0;
			long baseHealth = 100;
			int basePrice = 5;
			int pathNr = 0;
			PathNormal currentPath = null;
			Wave currentWave = null;
			String[] strings;

			while (next && (sLine = b.readLine()) != null) {
				if (sLine.trim().length() != 0) {
					switch (current) {
						case 5:

							strings = sLine.split(" ");
							TowerFactory.type towertype;
							int owner = 0;
							int row;
							int col;
							if (strings.length == 4) {
								try {
									towertype = TowerFactory.table.get(strings[0]);
									owner = new Integer(strings[1]).intValue();
									col = new Integer(strings[2]).intValue(); // x
									row = new Integer(strings[3]).intValue(); // y
									JsonTower t = new JsonTower();
									t.col = col;
									t.row = row;
									t.owner = owner;
									t.type = towertype;
									towers.add(t);
								} catch (NumberFormatException ex) {
									System.err.println("Warning, not a number in tower line!");
									ex.printStackTrace();
								}
							} else {
								System.err.println("Warning, error reading tower, expected '<type> <owner> <col> <row>' but got " + (strings.length - 1) + " values");
							}

							break;
						case 4:
							levelInfo += sLine + "\n";
							gameMap.setInfo(levelInfo);
							break;
						case 3:
							// We're reading a board
							count = 0;
							gridData.add(sLine);
							lineNr++;
							break;
						case 2:
							// We're reading a wave
							strings = sLine.split(" ");
							if (strings.length > 0) {
								currentWave.addEnemiesFromNames(strings, pathNr);
								pathNr++;
							} else {
								System.err.println("Warning, empty wave: " + strings.length + " enemies");
							}
							break;
						case 1:
							// We're reading a path
							strings = sLine.split(",");
							if (strings.length == 2) {
								try {
									Integer x = new Integer(strings[0]);
									Integer y = new Integer(strings[1]);
									currentPath.addStep(x.intValue(), y.intValue());
								} catch (NumberFormatException ex) {
									System.err.println("Warning, not a number in path!");
									ex.printStackTrace();
								}
							} else {
								System.err.println("Warning, error reading path, expected 2 but got " + strings.length + " values");
							}
							break;
						case 0:
							break;
						default:
							System.err.println("Warning, unknown state (" + current + ") reading map");
							break;
					}
				}
				if (sLine.trim().length() == 0) {
					switch (current) {
						case 1:
							// end path
							currentPath.finalise();
							paths.add(currentPath);
							break;
						case 2:
							// end wave
							waves.add(currentWave);
							break;
						case 3:
							// end board
							break;
					}
					current = 0;
				}
				if (current == 0) {
					if (sLine.startsWith("path")) {
						// start a new path
						strings = sLine.split(" ");
						float baseDelay = 0;
						int targetUser = 0;
						int owner = -1;
						if (strings.length == 2) {
							try {
								baseDelay = new Float(strings[1]).floatValue();
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in path baseDelay value!");
								ex.printStackTrace();
							}
						}
						if (strings.length == 4) {
							try {
								targetUser = new Integer(strings[2]).intValue();
								owner = new Integer(strings[3]).intValue();
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in path targetUser value!");
								ex.printStackTrace();
							}
						}
						current = 1;
						currentPath = new PathNormal(baseDelay, targetUser, owner);

					} else if (sLine.startsWith("wave")) {
						// start a new wave
						current = 2;
						pathNr = 0;
						strings = sLine.split(" ");
						if (strings.length == 3) {
							try {
								baseHealth = new Long(strings[1]).longValue();
								basePrice = new Integer(strings[2]).intValue();
								currentWave = new Wave(baseHealth, basePrice, waves.size() + 1, -1);
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in wave health value!");
								ex.printStackTrace();
							}
						} else if (strings.length == 4) {
							int baseScore = -1;
							try {
								baseHealth = new Long(strings[1]).longValue();
								basePrice = new Integer(strings[2]).intValue();
								baseScore = new Integer(strings[3]).intValue();
								currentWave = new Wave(baseHealth, basePrice, baseScore, -1);
								currentWave.setLevel(waves.size() + 1);
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in wave health, price or score value!");
								ex.printStackTrace();
							}
						} else {
							System.err.println("Warning, error reading wave, expected 'wave <health> <price> [score]' but got " + (strings.length - 1) + " values");
						}
					} else if (sLine.startsWith("level")) {
						// start of a new level!
						current = 3;
						gameMap.setTitle(sLine.substring(6));
						sLine = b.readLine();
						sLine = b.readLine();

						lineNr = 0;
					} else if (sLine.startsWith("budget")) {
						// budget declaration
						strings = sLine.split(" ");
						if (strings.length >= 2) {
							try {
								int budget = new Integer(strings[1]).intValue();
								gameMap.setBudget(budget);
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in budget value!");
								ex.printStackTrace();
							}
						} else {
							System.err.println("Warning, error reading budget, expected 'budget <budget> <baseInterest> <interestIncrease>' but got " + (strings.length - 1) + " values");
						}
						if (strings.length >= 4) {
							try {
								gameMap.setInterestStart(new Integer(strings[2]).intValue());
								gameMap.setInterestIncrease(new Integer(strings[3]).intValue());
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in budget value!");
								ex.printStackTrace();
							}
						}
					} else if (sLine.startsWith("lives")) {
						// lives declaration
						strings = sLine.split(" ");
						if (strings.length == 2) {
							try {
								gameMap.setLives(new Integer(strings[1]).intValue());
							} catch (NumberFormatException ex) {
								System.err.println("Warning, not a number in budget value!");
								ex.printStackTrace();
							}
						} else {
							System.err.println("Warning, error reading lives, expected 'lives <lives>' but got " + (strings.length - 1) + " values");
						}
					} else if (sLine.startsWith("background")) {
						// start of a new level!
						if (sLine.length() > 11) {
							gameMap.setBackGroundImage(sLine.substring(11));
						}
					} else if (sLine.startsWith("info")) {
						// Level info following
						current = 4;
					} else if (sLine.startsWith("towers")) {
						// start a new towers section
						current = 5;
					}
				}

			}
			switch (current) {
				case 1:
					// end path
					currentPath.finalise();
					paths.add(currentPath);
					break;
				case 2:
					// end wave
					waves.add(currentWave);
					break;
			}
			b.close();

		} catch (IOException e) {
			System.out.println("HexTD::readFile::IO exception: " + e.getLocalizedMessage());
		}

		return gameMap;
	}
}
